package server

import (
	"fmt"
	"github.com/gorilla/websocket"
	"github.com/nu7hatch/gouuid"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"
)

// PortBinder listens connection on a port, dispatch
// messages to Server, receive messages from Server
type portBinder struct {
	server *Server
	conns  map[string]*wsConn
}

// Create a SSLPortBinder
func newSSLPortBinder(server *Server) *portBinder {
	pb := new(portBinder)
	pb.server = server
	pb.conns = map[string]*wsConn{}
	return pb
}

func (b *portBinder) sendPing() {
	for {
		time.Sleep(60 * time.Second)
		for id, ww := range b.conns {
			if ww.ws == nil {
				b.closeConn(id)
				continue
			}
			data := []byte("")
			err := ww.ws.WriteControl(websocket.PingMessage, data, time.Now().Add(10*time.Second))
			if err != nil {
				ww.close()
				b.closeConn(id)
			}
		}
	}
}

func (b *portBinder) serveHTML(w http.ResponseWriter, r *http.Request) {
	html := "Welcome to Teleport! Use websocket handshake to connect."
	logByte, err1 := ioutil.ReadFile(b.server.logFile)
	if err1 == nil {
		lines := strings.Split(string(logByte), "\n")
		size := len(lines)
		html = ""
		for i, line := range lines {
			if i < size-100 {
				continue
			}
			html += line
			html += "\n"
		}
	}
	fmt.Fprintf(w, html)
}

// Main http connection handler
func (b *portBinder) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Upgrade to websocket connection
	ws, err := websocket.Upgrade(w, r, nil, 1024*1000, 1024*1000)
	if _, ok := err.(websocket.HandshakeError); ok {
		// Wrong handshake
		b.serveHTML(w, r)
		return
	} else if err != nil {
		log.Println("ServeHTTP: %s", err)
		panic(err)
		return
	}

	uid, _ := uuid.NewV4()
	id := uid.String()

	ww := newWsConn(ws, b, id)

	// Default dispose call
	defer func() {
		ww.close()
	}()

	b.conns[id] = ww

	// Start a write goroutine
	go ww.writePump()
	// Read messages from connection
	ww.readPump()
}

// Close a websocket connection by id
func (b *portBinder) closeConn(id string) {
	b.server.closeConn(id)
	if _, ok := b.conns[id]; ok {
		delete(b.conns, id)
	}
}

// Send TextMessages to connections by id
func (b *portBinder) sendPack(id string, p *Pack) {
	s, err := p.MarshalJSON()
	if err == nil {
		b.writeMessage(id, websocket.TextMessage, s)
	}
}

// Send BinaryMessages to connection by id
func (b *portBinder) sendBinary(id string, p []byte) {
	b.writeMessage(id, websocket.BinaryMessage, p)
}

// Write byte array to connection by id
func (b *portBinder) writeMessage(id string, messageType int, p []byte) {
	c, ok := b.conns[id]
	if !ok {
		b.closeConn(id)
		log.Println("conn with id: %s not found", id)
		return
	}
	if c.ws == nil {
		b.closeConn(id)
		return
	}

	addr := c.ws.RemoteAddr().String()

	if messageType == websocket.TextMessage {
		m, _ := Validate(string(p))
		b.server.logPack("send", m, addr)
	} else if messageType == websocket.BinaryMessage {
		b.server.logBinary("send", p, addr)
	}
	wp := new(WriteStruct)
	wp.T = messageType
	wp.Data = p
	c.send <- wp
}

// Start listening at specified address
func (b *portBinder) start() {
	go b.sendPing()
	log.Println("teleport server listening at: wss://localhost:%s", strconv.Itoa(PORT))
	http.Handle("/", b)
	err := http.ListenAndServeTLS(":"+strconv.Itoa(PORT), b.server.cert, b.server.key, nil)
	if err != nil {
		log.Patal("try run with sudo: ListenAndServeTLS: %s", err)
		panic(err)
	}
}
